home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-05-14 | 16.2 KB | 581 lines | [TEXT/CWIE] |
- //
- // CVehicle.cp
- //
- // Maintain a "Vehicle" (camera and two headlights)
- //
- // By James Jennings
- // Started July 8, 1996
- //
-
- #include "CVehicle.h"
- #include "CVehicleViewPane.h"
- #include "Q3Utilities.h"
-
- const MessageT msg_ThrustValueMessage = 'Thru'; // sent by the "Thrust" slider
-
- const TQ3CameraPlacement kStart = { // camera starting position
- { 0, 0, 0 }, // cameraLocation
- { 0, 0, 3 }, // pointOfInterest
- { 0, 1, 0 } // upVector
- };
- const float kLightOffset = 0.4;
- const TQ3Vector3D kZero = { 0, 0, 0 };
- const TQ3Vector3D kUp = { 0, 1, 0 };
- const float kInitialThrottle = .25; // must match initial slider setting
-
- #pragma mark === CSpotLightMaker ===
-
- CSpotLight::CSpotLight( TQ3CameraPlacement inPlace, float inOffset )
- {
- // set light data
-
- mData.lightData.isOn = kQ3True;
- mData.lightData.brightness = 1.0;
- ::Q3ColorRGB_Set( &mData.lightData.color, 1, 1, 1 );
-
- // set spot light specific stuff
-
- mData.castsShadows = kQ3False;
- mData.attenuation = kQ3AttenuationTypeInverseDistance;
-
- // Default location and direction: Will be moved with transforms later.
- SetInitialPlacement( inPlace, inOffset );
-
- mData.hotAngle = Q3Math_DegreesToRadians( 25 );
- mData.outerAngle = Q3Math_DegreesToRadians( 40 );
- #if 1
- // Note: kQ3FallOffTypeExponential is an error to the debugging QD3D 1.0.6.
- mData.fallOff = kQ3FallOffTypeCosine;
- #else
- mData.fallOff = kQ3FallOffTypeLinear;
- #endif
- }
-
- void CSpotLight::Make()
- {
- Q3Forget( mObject );
- mObject = ::Q3SpotLight_New( &mData );
- ThrowIfNil_(mObject);
- }
-
- void CSpotLight::Transform( TQ3Matrix4x4 &inM )
- {
- Get(); // make sure that Make() has been called at least once
-
- ::Q3Point3D_Transform( &mStartLocation, &inM, &mData.location );
- ::Q3Vector3D_Transform( &mStartDirection, &inM, &mData.direction );
-
- TQ3Status status;
- status = ::Q3SpotLight_SetLocation( mObject, &mData.location );
- ThrowIfQ3Fail_( status );
- status = ::Q3SpotLight_SetDirection( mObject, &mData.direction );
- ThrowIfQ3Fail_( status );
- }
-
- void CSpotLight::SetInitialPlacement( const TQ3CameraPlacement inPlace, float inOffset )
- {
- // Set the light next to the camera pointing in the same direction.
- // This affects the mStartLocation and mStartDirection,
- // which the transformed mData is calculated from.
- // inOffset is how far to the right of the camera to place the light.
-
- // Find mStartDirection
- ::Q3Point3D_Subtract( &inPlace.pointOfInterest, &inPlace.cameraLocation, &mStartDirection );
- ::Q3Vector3D_Normalize( &mStartDirection, &mStartDirection );
-
- // Place the light slightly offset from the camera placement
- TQ3Vector3D right;
- Q3::Vector3D_CompleteBasis( &mStartDirection, &inPlace.upVector, &right );
- ::Q3Vector3D_Scale( &right, inOffset, &right );
-
- ::Q3Point3D_Vector3D_Add( &inPlace.cameraLocation, &right, &mStartLocation );
-
- // Sync mData and the spotlight with the new placement.
- mData.location = mStartLocation;
- mData.direction = mStartDirection;
-
- if (mObject != nil) {
- // Since mObject isn't made until it's needed, it might not exist yet.
- TQ3Status theStatus;
- theStatus = ::Q3SpotLight_SetLocation( mObject, &mData.location );
- ThrowIfQ3Fail_( theStatus );
- theStatus = ::Q3SpotLight_SetDirection( mObject, &mData.direction );
- ThrowIfQ3Fail_( theStatus );
- }
- }
-
- void CSpotLight::TurnOn( TQ3Boolean inOn )
- {
- // Set the object directly
- TQ3Status status = ::Q3Light_SetState( Get(), inOn );
- ThrowIfQ3Fail_(status);
-
- // Set our local data as well
- mData.lightData.isOn = inOn;
- }
-
-
- #pragma mark === CVehicle ===
-
- CVehicle::CVehicle(CVehicleViewPane *mViewPane)
- : mViewPane(mViewPane), mCamera(0), mHeadlights(0),
- mLeftLight(kStart, -kLightOffset),
- mRightLight(kStart, kLightOffset),
- mMergedLights(false), mRollIsStabilized(true), mLocalVertical(kUp),
- mVelocity(kZero), mYawVelocity(0), mPitchVelocity(0), mRollVelocity(0),
- mThrust(0.2), mThrottle(kInitialThrottle)
- {
- // All QD3D objects are created as needed.
- // (This makes the constructor exception safe.)
-
- SetPlacement(kStart);
- }
-
- CVehicle::~CVehicle()
- {
- Q3Forget( mCamera );
- Q3Forget( mHeadlights );
- }
-
- TQ3CameraObject CVehicle::GetCamera() const
- {
- if (mCamera==nil) {
- // GetCamera() is in principle const, but since we're defering
- // the creation of the camera, we need to cast away the constantness.
- TQ3CameraObject &camera = const_cast<CVehicle*>(this)->mCamera;
-
- SDimension16 frameSize;
- mViewPane->GetFrameSize( frameSize );
-
- CCameraMaker maker( frameSize );
- camera = maker.GetRef();
-
- TQ3Status theStatus = ::Q3Camera_SetPlacement(camera, &mPlacement);
- ThrowIfQ3Fail_( theStatus );
- }
-
- return mCamera;
- }
-
- void CVehicle::SetCamera(TQ3CameraObject inCamera)
- {
- // We don't want an outsider to change our camera type
- // so we only copy those camera properites that are reasonable.
- Assert_(inCamera != nil);
- TQ3Status theStatus;
-
- // We need to pass camera placement data to other structures.
- TQ3CameraPlacement thePlace;
- theStatus = ::Q3Camera_GetPlacement( inCamera, &thePlace );
- Assert_(theStatus == kQ3Success);
- SetPlacement(thePlace);
-
- }
-
- void CVehicle::SetPlacement(const TQ3CameraPlacement &inPlace)
- {
- // Move the camera to a particular place.
- // We move the start placement to that place and clear the transform.
-
- // Validation might change inPlace, and inPlace is const: Make a local copy.
- TQ3CameraPlacement thePlace = inPlace;
- if ( Q3::CameraPlacement_Validate(&thePlace) == kQ3False ) return;
-
- mStartPlacement = thePlace;
- mPlacement = thePlace;
- ::Q3Matrix4x4_SetIdentity(&mMatrix); // the transform from mStartPlacement to mPlacement
-
- mLeftLight.SetInitialPlacement(thePlace, (mMergedLights ? 0 : -kLightOffset));
- mRightLight.SetInitialPlacement(thePlace, (mMergedLights ? 0 : kLightOffset));
-
- // Send the camera to the new position.
- if (mCamera!=nil) {
- // Since the camera is only made when needed, it might not exist yet.
- TQ3Status theStatus = ::Q3Camera_SetPlacement( mCamera, &mPlacement );
- ThrowIfQ3Fail_( theStatus );
- }
- }
-
- void CVehicle::AddHeadlightsToGroup( TQ3GroupObject ioGroup ) const
- {
- // When most of our lights are from an outside source,
- // we still want to be able to add our headlights to it.
- Assert_(ioGroup != nil);
-
- TQ3GroupPosition pos;
- pos = ::Q3Group_AddObject( ioGroup, mLeftLight.Get() );
- ThrowIf_(pos==0);
-
- pos = ::Q3Group_AddObject( ioGroup, mRightLight.Get() );
- ThrowIf_(pos==0);
- }
-
- void CVehicle::SetLightMode( CommandT inLightMode )
- {
- switch( inLightMode ) {
- case cmd_LightsOff:
- mLeftLight.TurnOn( kQ3False );
- mRightLight.TurnOn( kQ3False );
- break;
- case cmd_LeftLight:
- mLeftLight.TurnOn( kQ3True );
- mRightLight.TurnOn( kQ3False );
- break;
- case cmd_RightLight:
- mLeftLight.TurnOn( kQ3False );
- mRightLight.TurnOn( kQ3True );
- break;
- case cmd_BothLights:
- mLeftLight.TurnOn( kQ3True );
- mRightLight.TurnOn( kQ3True );
- break;
- case cmd_CenteredLights:
- mLeftLight.SetInitialPlacement( mStartPlacement, 0 );
- mLeftLight.Transform( mMatrix );
- mRightLight.SetInitialPlacement( mStartPlacement, 0 );
- mRightLight.Transform( mMatrix );
- mMergedLights = true;
- break;
- case cmd_SeparatedLights:
- mLeftLight.SetInitialPlacement( mStartPlacement, -kLightOffset );
- mLeftLight.Transform( mMatrix );
- mRightLight.SetInitialPlacement( mStartPlacement, kLightOffset );
- mRightLight.Transform( mMatrix );
- mMergedLights = false;
- break;
- default:
- SignalPStr_("\pBad Light Mode");
- break;
- }
- }
-
- CommandT CVehicle::GetHeadlightState(void) const
- {
- Boolean left = mLeftLight.IsOn();
- Boolean right = mRightLight.IsOn();
- if (left && right) {
- return cmd_BothLights;
- } else if (left) {
- return cmd_LeftLight;
- } else if (right) {
- return cmd_RightLight;
- }
- return cmd_LightsOff;
- }
-
- #pragma mark === Motion Control ===
-
- void CVehicle::SetScale(float inScale)
- {
- // The scale is the scale of the scene,
- // usually the diagonal of the bounding box.
- // It determines the thrust so that we can navigate
- // the scene in a reasonable amount of time.
-
- // 0.01 is a hand tuned adjustment
- mThrust = inScale * 0.01;
-
- // Adjust the camera range to the scale.
- // (These numbers are rough guesses.)
- // Note: values of scale/1000 and scale*100 got reports of problems
- // in a large "hierarchical trigrid". I think that scale/1000 and scale*10
- // is the least I can get away with.
- TQ3CameraRange range;
- range.hither = inScale/1000;
- range.yon = inScale*15;
- TQ3Status theStatus = ::Q3Camera_SetRange(GetCamera(), &range);
- Assert_(theStatus==kQ3Success);
- }
-
- Boolean CVehicle::Boost( float inT, Int16 inSignX, Int16 inSignY, Int16 inSignZ )
- { // Accelerate the vehicle in any of the above directions.
- // inT is the number of tick for this iteration
- // inSignX, inSignY, and inSignZ are the signs of the accelerations
- // in each direction.
- // Return true if we move and need to redraw the view.
- Boolean moved = false;
-
- float accel = GetThrust();
- float damping = 0.25;
- float delta;
-
- delta = CalcDynamicParam( inT, mVelocity.x, inSignX, accel, damping );
- if (delta != 0.0) {
- MoveRightBy( delta );
- moved = true;
- }
-
- delta = CalcDynamicParam( inT, mVelocity.y, inSignY, accel, damping );
- if (delta != 0.0) {
- MoveForwardBy( delta );
- moved = true;
- }
-
- delta = CalcDynamicParam( inT, mVelocity.z, inSignZ, accel, damping );
- if (delta != 0.0) {
- MoveUpBy( delta );
- moved = true;
- }
-
- return moved;
- }
-
- Boolean CVehicle::Spin( float inT, Int16 inSignY, Int16 inSignP, Int16 inSignR )
- {
- Boolean moved = false;
-
- float accel = Q3Math_DegreesToRadians(1);
- float damping = 0.25;
- float delta;
-
- delta = CalcDynamicParam( inT, mYawVelocity, inSignY, accel, damping );
- if (delta != 0.0) {
- YawBy( delta );
- moved = true;
- }
-
- delta = CalcDynamicParam( inT, mPitchVelocity, inSignP, accel, damping );
- if (delta != 0.0) {
- PitchBy( delta );
- moved = true;
- }
-
- delta = CalcDynamicParam( inT, mRollVelocity, inSignR, accel, damping );
- if (delta != 0.0) {
- RollBy( delta );
- moved = true;
- }
-
- return moved;
- }
-
- float CVehicle::GetThrust()
- {
- // mThrust was determined by a call to SetScale()
- // mThrottle was determined by a user-controlled slider.
- return mThrust * mThrottle;
- }
-
- float CVehicle::CalcDynamicParam(float t, float &v, Int16 sign, float a, float damp)
- {
- // t = number of ticks this iteration.
- // v = the input and output velocity
- // a = the acceleration/boost
- // sign = sign of the acceleration (depends on modifier keys)
- // sign == 0: no modifiers
- // damp = between 0 and 1. Bigger --> more breaking when the key is released
- // damp also controls the maximum velocity.
- // Return the amount to move by.
-
- // ••• Uses Newtonian mechanics •••
- // v = v0 + a * t - damping(v,t)
- // d = d0 + v * t
- // The numerical integration is pretty rough, and gets rougher for large t
- // (and t is large when rendering large models) but it's good enough for
- // the user interface.
-
- Assert_( 0.0 < damp && damp <= 1.0 );
- Assert_( t >= 0 );
-
- // Limit the apparent number of ticks between iterations
- // so things don't get too difficult when the machine can't keep up.
- if (t > 6) t = 6;
-
- // We accumulate velocity over time, limited by damping.
- // Damping also slows us down when we release the keys.
- v += sign * a * t;
- for (Int16 i=0; i<t; i++)
- v *= 1 - damp; // (damping depends on time)
-
- // Special case: slow velocity and no modifier keys. Stop.
- if ( sign == 0 && -a < v && v < a )
- v = 0;
-
- return v*t;
- }
-
- void CVehicle::SetRollStabilized(Boolean inStabilized)
- {
- mRollIsStabilized = inStabilized;
- if (inStabilized) {
- mLocalVertical = mPlacement.upVector;
- // the upVector is supposed to be normalized, but just in case
- // ::Q3Vector3D_Normalize( &mLocalVertical, &mLocalVertical );
- }
- }
-
- Boolean CVehicle::StabilizeRoll()
- {
- if (!RollIsStabilized()) return false;
-
- // Keep our view horizontal.
- // A mix of Yaw and Pitch can result in an accidental Roll.
- // Call this function to remove it.
- // Returns true if anything changed.
-
- // GOAL: make sure our left-right axis is orthogonal to mLocalVertical
- // u = old left right axis
- // v = local vertical (the upVector when stabilizing was turn on)
- // u' = u - (u.v)v
- TQ3Vector3D oldRight, newRight, temp;
- GetRightVector(oldRight);
-
- // These asserts are a nice idea, but they're too sensitive to roundoff errors.
- // Assert_( fabs(::Q3Vector3D_Length( &mLocalVertical ) - 1) <= kQ3RealZero );
- // Assert_( fabs(::Q3Vector3D_Length( &oldRight ) - 1) <= kQ3RealZero );
- ::Q3Vector3D_Normalize( &mLocalVertical, &mLocalVertical ); // just in case
-
- float dot = ::Q3Vector3D_Dot( &mLocalVertical, &oldRight );
-
- // If we're already orthogonal, stop.
- if ( fabs(dot) <= kQ3RealZero ) return false;
-
- Assert_( -1 <= dot && dot <= 1 );
- float angle = kQ3Pi/2 - acos(dot); // angle to rotate by
-
- // Turn off roll stabilization while we roll
- // so that RollBy() won't change mLocalVertical
- // (not necessary at the moment, but it might be sometime)
- mRollIsStabilized = false;
- RollBy(angle);
- mRollIsStabilized = true;
-
- return true;
- }
-
- void CVehicle::GetUpVector( TQ3Vector3D &outUp )
- { // 'up' in vehicle coordinates, normalized
- outUp = mPlacement.upVector;
- // It shouldn't be necessary to normalize
- ::Q3Vector3D_Normalize( &outUp, &outUp );
- }
-
- void CVehicle::GetForwardVector( TQ3Vector3D &outFwd )
- { // 'Forward' in vehicle coordinates, normalized
- ::Q3Point3D_Subtract( &mPlacement.pointOfInterest, &mPlacement.cameraLocation, &outFwd );
- ::Q3Vector3D_Normalize( &outFwd, &outFwd );
- }
-
- void CVehicle::GetRightVector( TQ3Vector3D &outRight )
- { // 'Right' in vehicle coordinates, normalized
- TQ3Vector3D forward;
- GetForwardVector(forward);
- Q3::Vector3D_CompleteBasis( &forward, &mPlacement.upVector, &outRight );
- }
-
- void CVehicle::MoveForwardBy( float inDistance )
- {
- // calculate the vector to move by
- TQ3Vector3D d;
- GetForwardVector( d );
- ::Q3Vector3D_Scale( &d, inDistance, &d );
-
- TQ3Matrix4x4 M;
- ::Q3Matrix4x4_SetTranslate( &M, d.x, d.y, d.z );
- ApplyMatrix( M );
- }
-
- void CVehicle::MoveRightBy( float inDistance )
- {
- // calculate the vector to move by
- TQ3Vector3D right;
- GetRightVector( right );
- ::Q3Vector3D_Scale( &right, inDistance, &right );
-
- TQ3Matrix4x4 M;
- ::Q3Matrix4x4_SetTranslate( &M, right.x, right.y, right.z );
- ApplyMatrix( M );
- }
-
- void CVehicle::MoveUpBy( float inDistance )
- {
- // calculate the vector to move by
- TQ3Vector3D up;
- GetUpVector( up );
- ::Q3Vector3D_Scale( &up, inDistance, &up );
-
- TQ3Matrix4x4 M;
- ::Q3Matrix4x4_SetTranslate( &M, up.x, up.y, up.z );
- ApplyMatrix( M );
- }
-
- void CVehicle::YawBy( float inAngle )
- {
- TQ3Vector3D v;
- GetUpVector( v );
-
- TQ3Matrix4x4 M;
- ::Q3Matrix4x4_SetRotateAboutAxis( &M, &mPlacement.cameraLocation, &v, inAngle );
- ApplyMatrix( M );
- }
-
- void CVehicle::PitchBy( float inAngle )
- {
- TQ3Vector3D v;
- GetRightVector( v );
-
- TQ3Matrix4x4 M;
- ::Q3Matrix4x4_SetRotateAboutAxis( &M, &mPlacement.cameraLocation, &v, inAngle );
- ApplyMatrix( M );
- }
-
- void CVehicle::RollBy( float inAngle )
- {
- // I'd like to "tweak" this for when RollIsStabilized(), but
- // the things I've tried haven't worked very well.
- // Perhaps it's best to just leave it.
- TQ3Vector3D v;
- GetForwardVector( v );
-
- // Why is mLocalVertical parallel to v?
- // if (RollIsStabilized()) { // project 'forward' to the horizontal plane
- // Q3::Vector3D_MakeOrthogonal( &mLocalVertical, &v, &v );
- // }
-
- TQ3Matrix4x4 M;
- ::Q3Matrix4x4_SetRotateAboutAxis( &M, &mPlacement.cameraLocation, &v, inAngle );
- ApplyMatrix( M );
-
- // if (RollIsStabilized()) { // Adjust the local vertical. How?
- // ::Q3Vector3D_Transform( &mLocalVertical, &M, &mLocalVertical );
- // }
- }
-
- void CVehicle::ApplyMatrix( TQ3Matrix4x4 &M )
- {
- Assert_( mCamera != nil );
-
- // update and apply the vehicle's transform
- ::Q3Matrix4x4_Multiply( &mMatrix, &M, &mMatrix );
-
- mLeftLight.Transform( mMatrix );
- mRightLight.Transform( mMatrix );
-
- // update and apply the camera's placement
- Q3::CameraPlacement_Transform( &mStartPlacement, &mMatrix, &mPlacement );
-
- TQ3Status status = ::Q3Camera_SetPlacement( mCamera, &mPlacement );
- ThrowIfQ3Fail_( status );
-
- }
-
- #pragma mark === LListener methods ===
-
- void CVehicle::ListenToMessage(MessageT inMessage, void *ioParam)
- {
- const float minThrottle = 0.01;
- const float maxThrottle = 1.0;
- const Int32 maxSlider = 100; // minSlider = 0
-
- switch (inMessage) {
- case msg_ThrustValueMessage:
- // convert the slider setting to a throttle value
- Int32 theValue = *(Int32*)ioParam;
- mThrottle = minThrottle + ((float)theValue) * (maxThrottle - minThrottle) / maxSlider;
- Assert_( mThrottle >= minThrottle && mThrottle <= maxThrottle );
- break;
- default:
- break;
- }
- }
-